﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace PMatch.Formats.Conversion
{
	/// <summary>
	/// Indica el comportamiento de la conversion UniProt a SPMatch
	/// </summary>
	public enum ConversionMode
	{
		/// <summary>
		/// Toma solo la informacion de los feature "chain" para obtener los inicios de cadena aun cuando las
		/// cantidades de sites y chains sean inconsistentes
		/// </summary>
		TakeChainsOnly,
		/// <summary>
		/// Toma solo la informacion de los feature "site" para obtener los inicios de cadena aun cuando las
		/// cantidades de sites y chains sean incosistentes
		/// </summary>
		TakeSitesOnly,
		/// <summary>
		/// Retorna un resultado sin secuencia cuando la informacion es incosistente
		/// </summary>
		SkipOnMismatch,
		/// <summary>
		/// Toma la informaicon de los feature "site" o "chain" en funcion de su presencia en la estructura.
		/// si ambos conjuntos de feature estan presentes pero sus cantidades son inconsistentes el programa
		/// tomara la informacion de los chains
		/// </summary>
		TakeChainsOnMismatch,
		/// <summary>
		/// Toma la informaicon de los feature "site" o "chain" en funcion de su presencia en la estructura.
		/// si ambos conjuntos de feature estan presentes pero sus cantidades son inconsistentes el programa
		/// tomara la informacion de los sites
		/// </summary>
		TakeSitesOnMismatch
	}

	/// <summary>
	/// Funciones utilitarias para convertir entre formatos de archivo UniProt a SPMatch
	/// </summary>
	public class UniProtToSPMatchConverter
	{

		/// <summary>
		/// Convierte una entry del formato UniProt al formato SPMatch
		/// </summary>		
		public static ConversionResult Convert(UniProt.entry input, ConversionMode mode = ConversionMode.SkipOnMismatch)
		{
			var seq = new sPMatch.sequence();
			seq.accession = input.accession.FirstOrDefault();
			seq.content = input.GetRawString();
			var chains = input.feature == null ? null : input.feature.Where(x => x.type == UniProt.featureTypeType.chain);
			var sites = input.feature == null ? null : input.feature.Where(x => x.type == UniProt.featureTypeType.site);
			var nchains = chains != null ? chains.Count() : 0;
			var nsites = sites != null ? sites.Count() : 0;

			var mismatch = nchains > 0 && nsites > 0 && nchains != nsites + 1;
			var result = new ConversionResult() { Mismatch = mismatch, Sequence = seq };

			if (mode == ConversionMode.SkipOnMismatch && mismatch)
			{
				return result;
			}
			else if (nchains > 0 || mode == ConversionMode.TakeChainsOnly || (mode == ConversionMode.TakeChainsOnMismatch && mismatch))
			{
				seq.chain.AddRange(chains.Select(x =>
				{
					var beginIndexItem = x.location.ItemsElementName.ToList().IndexOf(UniProt.ItemsChoiceType1.begin);
					return new sPMatch.sequenceChain() { start = (int)x.location.Items[beginIndexItem].position };
				}));
			}
			else if (nsites > 0 || mode == ConversionMode.TakeSitesOnly || (mode == ConversionMode.TakeSitesOnMismatch && mismatch))
			{
				seq.chain.AddRange(sites.Select(x =>
				{
					var beginIndexItem = x.location.ItemsElementName.ToList().IndexOf(UniProt.ItemsChoiceType1.end);
					return new sPMatch.sequenceChain() { start = (int)x.location.Items[beginIndexItem].position };
				}));
			}

			return result;
		}

		/// <summary>
		/// Convierte un archivo del formato Uniprot (elemento raiz 'entry') que contiene una unica secuencia
		/// al formato SPMatch
		/// </summary>
		/// <param name="inFilename"></param>
		/// <param name="outFilename"></param>
		public static void ConvertEntryFile(string inFilename, string outFilename)
		{
			var entry = UniProt.entry.LoadFromFile(inFilename);
			var seq = Convert(entry);
			seq.Sequence.SaveToFile(outFilename);
		}

		/// <summary>
		/// Convierte un archivo en formato Uniprot (elemento raiz 'uniprot') que contiene una
		/// coleccion de secuencias al formato SPMatch
		/// </summary>
		/// <param name="inFilename"></param>
		/// <param name="outFilename"></param>
		public static IEnumerable<ConversionResult> ConvertFile(string inFilename, string outFilename, ConversionMode mode = ConversionMode.SkipOnMismatch, Predicate<ConversionResult> selector = null)
		{
			var entries = UniProt.uniprot.LoadFromFile(inFilename);
			var sequences = new sPMatch.sequences();
			foreach (var entry in entries.entry)
			{
				var seq = Convert(entry, mode);
				if (selector != null && !selector(seq)) continue;
				sequences.sequence.Add(seq.Sequence);
				yield return seq;
			}
			sequences.SaveToFile(outFilename);
		}

		/// <summary>
		/// Convierte un archivo en formato Uniprot (elemento raiz 'uniprot') que contiene una
		/// coleccion de secuencias al formato SPMatch
		/// </summary>
		/// <param name="inFilename"></param>
		/// <param name="outFilename"></param>
		public static IEnumerable<ConversionResult> ConvertToSingleFiles(string inFilename, string outDir, ConversionMode mode = ConversionMode.SkipOnMismatch, Predicate<ConversionResult> selector = null)
		{
			var entries = UniProt.uniprot.LoadFromFile(inFilename);
			foreach (var entry in entries.entry)
			{
				var seq = Convert(entry, mode);
				if (selector != null && !selector(seq)) continue;
				var fn = Path.Combine(outDir, seq.Sequence.accession + ".xml");
				seq.Sequence.SaveToFile(fn);
				yield return seq;
			}
		}

		public static IEnumerable<ConversionResult> ConvertFile(ConversionSettings settings)
		{
			Predicate<ConversionResult> selector = x =>
				!(settings.FilterMinChains.HasValue && x.Sequence.chain.Count < settings.FilterMinChains) &&
				!(settings.FilterMaxChains.HasValue && x.Sequence.chain.Count > settings.FilterMaxChains);
			if (settings.MultipleFiles)
			{
				var r = ConvertToSingleFiles(settings.InputFile, settings.OutputPath, settings.Mode, selector);
				return r;
			}
			else
			{
				var r = ConvertFile(settings.InputFile, settings.OutputPath, settings.Mode, selector);
				return r;
			}
		}
	}

	public class ConversionSettings
	{
		public ConversionSettings()
		{
			Mode = ConversionMode.SkipOnMismatch;
		}

		public bool MultipleFiles { get; set; }
		public int? FilterMinChains { get; set; }
		public int? FilterMaxChains { get; set; }
		public string InputFile { get; set; }
		public string OutputPath { get; set; }
		public ConversionMode Mode { get; set; }
	}

	/// <summary>
	/// Resultado de una conversion de secuencia
	/// </summary>
	public class ConversionResult
	{
		/// <summary>
		/// Secuencia
		/// </summary>
		public sPMatch.sequence Sequence { get; set; }

		/// <summary>
		/// Indica si durante la conversion se detecto inconsistencias en el formato de 
		/// origen
		/// </summary>
		public bool Mismatch { get; set; }
	}
}
